﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace game
{
    public class OldParticleSystem
    {
        #region Particle Struct

        protected struct Particle
        {
            public Vector3 initialPosition;
            public Vector3 initialVelocity;
            public float initialSize; // In pixels.
            public float initialTimeMs;
            public float dieLifeTimeMs;
            // float mass;
            public Color initialColor;

            public static VertexElement[] VertexElements;
            public static VertexDeclaration VertexDecl;
            public static int SizeInBytes { get; private set; }

            static Particle()
            {
                VertexElements = new VertexElement[] { // must have the same format as Particle struct
                    new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
                    new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
                    new VertexElement(0, 24, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1),
                    new VertexElement(0, 28, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2),
                    new VertexElement(0, 32, VertexElementFormat.Single, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3),
                    new VertexElement(0, 36, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0) 
                };

                VertexDecl = new VertexDeclaration(ShooterGame.Instance.GraphicsDevice, VertexElements);

                SizeInBytes = 12 + 12 + 4 + 4 + 4 + 4;
            }
        }

        #endregion

        #region Fields

        public float MinInitialSpeed { get; set; }
        public float MaxInitialSpeed { get; set; }

        public float MinInitialSize { get; set; }
        public float MaxInitialSize { get; set; }

        public float MinLifeTimeMs { get; set; }
        public float MaxLifeTimeMs { get; set; }

        public float EmitTimeMs { get; set; }
        public float Acceleration { get; set; }

        public Matrix World
        {
            get { return world; }
            set { world = value; invWorldCurrent = false;  }
        }
        protected Matrix world;
        protected Matrix invWorld;
        protected bool invWorldCurrent = false;

        protected Texture texture;
        protected DynamicVertexBuffer vb;
        protected DynamicIndexBuffer ib;

        protected int elapsedSystemTimeMs;
        protected short maxParticles;

        protected Particle[] particles;
        protected List<short> aliveParticles;
        protected Queue<short> deadParticles;

        protected Scene scene = null;

        protected Effect effect;
        protected EffectTechnique effectTechnique;
        protected EffectParameter ep_gWVP;
        protected EffectParameter ep_gTex;
        protected EffectParameter ep_gEyePosL;
        protected EffectParameter ep_gAccel;
        protected EffectParameter ep_gPSystemTime;
        protected EffectParameter ep_gViewportHeight;

        #endregion

        #region Effect

        protected virtual void SetEffectParamHandles()
        {
            ep_gWVP = effect.Parameters["gWVP"];
            ep_gTex = effect.Parameters["gTex"];
            ep_gEyePosL = effect.Parameters["gEyePosL"];
            ep_gAccel = effect.Parameters["gAccel"];
            ep_gPSystemTime = effect.Parameters["gPSystemTime"];
            ep_gViewportHeight = effect.Parameters["gViewportHeight"];
        }

        protected virtual void SetEffectParams()
        {
            Camera camera = scene.ActiveCamera;
            ep_gWVP.SetValue(camera.View * camera.Projection);
            ep_gEyePosL.SetValue(camera.Position);
            ep_gPSystemTime.SetValue(elapsedSystemTimeMs);
            ep_gViewportHeight.SetValue(
                ShooterGame.Instance.GraphicsDevice.Viewport.Height);
            ep_gAccel.SetValue(Acceleration);
            ep_gTex.SetValue(texture);
        }

        #endregion

        #region Init

        protected virtual void InitParameters()
        {
            MinInitialSpeed = 0.0f;
            MaxInitialSpeed = 0.0f;
            MinInitialSize = 50;
            MaxInitialSize = 100;
            MinLifeTimeMs = 1000;
            MaxLifeTimeMs = 5000;
        }

        #endregion

        #region Ctor

        protected OldParticleSystem(Scene scene, String fxName, String techniqueName,
            String texName, float acc, short maxParticles, float emitTime)
        {
            this.scene = scene;
            this.Acceleration = acc;
            this.maxParticles = maxParticles;
            this.EmitTimeMs = emitTime;
            this.elapsedSystemTimeMs = 0;

            InitParameters();

            World = Matrix.Identity;

            particles = new Particle[maxParticles];

            for (int i=0; i<particles.Length; i++)
            {
                particles[i].dieLifeTimeMs = -1; // indicates that particle is "dead"
            }

            texture = ShooterGame.Instance.Content.Load<Texture>(texName);
            effect = ShooterGame.Instance.Content.Load<Effect>(fxName);
            effectTechnique = effect.Techniques[techniqueName];

            SetEffectParamHandles();

            vb = new DynamicVertexBuffer(ShooterGame.Instance.GraphicsDevice,
                Particle.SizeInBytes * maxParticles, BufferUsage.Points);

            ib = new DynamicIndexBuffer(ShooterGame.Instance.GraphicsDevice, typeof(short),
                maxParticles, BufferUsage.Points);

            aliveParticles = new List<short>();
            deadParticles = new Queue<short>();
            for (short i = 0; i < maxParticles; i++)
            {
                deadParticles.Enqueue(i);
            }
        }

        #endregion

        #region Particles-manipulate methods

        /// <summary>
        /// </summary>
        /// <param name="count">number of particles to add</param>
        /// <returns>number of particles method was unable to add</returns>
        public int AddParticles(int count)
        {
            int index = -1;
            while (deadParticles.Count > 0 && count > 0)
            {
                index = deadParticles.Dequeue();
                InitParticle(index);
                count--;
            }
            return count;
        }

        /// <summary>
        /// Might or might not be implemented by derived classes.
        /// Correct place to put some initial particle-adding code in case of
        /// non-continuous particle systems like explosion.
        /// User of particle system should call it whenever he wants
        /// an effect to initiate.
        /// </summary>
        public virtual void Start()
        {
        }

        protected virtual void InitParticle(int index)
        {
            particles[index].initialTimeMs = elapsedSystemTimeMs;
            particles[index].initialSize
                = RandomHelper.RandomBetween(MinInitialSize, MaxInitialSize);
            particles[index].dieLifeTimeMs
                = RandomHelper.RandomBetween(MinLifeTimeMs, MaxLifeTimeMs);
            particles[index].initialColor = new Color(3, 2, 1, 0);
        }

        #endregion

        #region Update

        private float emitTimeAccumMs = 0;
        public void Update(GameTime gameTime)
        {
            elapsedSystemTimeMs += gameTime.ElapsedGameTime.Milliseconds;

            aliveParticles.Clear();
            deadParticles.Clear();

            for (short i = 0; i < maxParticles; i++)
            {
                if (elapsedSystemTimeMs - particles[i].initialTimeMs > particles[i].dieLifeTimeMs)
                {
                    deadParticles.Enqueue(i);
                }
                else
                {
                    aliveParticles.Add(i);
                }
            }

            if (EmitTimeMs > 0.0f)
            {
                int pCount = 0;
                emitTimeAccumMs += gameTime.ElapsedGameTime.Milliseconds;
                while (emitTimeAccumMs >= EmitTimeMs)
                {
                    emitTimeAccumMs -= EmitTimeMs;
                    pCount++;
                }

                if (pCount > 0)
                {
                    AddParticles(pCount);
                }
            }
        }

        #endregion

        #region Draw

        public virtual void Draw(GameTime gameTime)
        {
            //vb.SetData(
            if (aliveParticles.Count <= 0)
            {
                return;
            }

            if (!invWorldCurrent)
            {
                invWorld = Matrix.Invert(world);
                invWorldCurrent = true;
            }

            SetEffectParams();

            vb.SetData(particles, 0, particles.Length, SetDataOptions.Discard);
            short[] a = aliveParticles.ToArray();
            ib.SetData(a, 0, a.Length, SetDataOptions.Discard);
            //for (int i = 0; i < aliveParticles.Count; i++)
            //{
            //    vb.SetData(0, particles, i, 1, Particle.SizeInBytes,
            //        SetDataOptions.NoOverwrite);
            //}
            ShooterGame.Instance.GraphicsDevice.RenderState.PointSizeMax = 256.0f;

            effect.CurrentTechnique = effectTechnique;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                ShooterGame.Instance.GraphicsDevice.Vertices[0].SetSource(vb, 0,
                    Particle.SizeInBytes);
                ShooterGame.Instance.GraphicsDevice.Indices = ib;
                ShooterGame.Instance.GraphicsDevice.VertexDeclaration 
                    = Particle.VertexDecl;
                //ShooterGame.Instance.GraphicsDevice.DrawPrimitives(PrimitiveType.PointList,
                //    0, maxParticles);
                ShooterGame.Instance.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.PointList, 0, 0, maxParticles, 0, aliveParticles.Count);
                pass.End();
            }
            effect.End();
        }

        #endregion

    }
}
